package com.wechat.aspect;

import com.wechat.annotation.GlobalInterceptor;
import com.wechat.entity.constants.Constants;
import com.wechat.entity.dto.TokenUserInfoDto;
import com.wechat.entity.enums.ResponseCodeEnum;
import com.wechat.exception.BusinessException;
import com.wechat.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 全局拦截器----切面，对登录、权限、参数校验进行拦截
 * 被拦截：抛出异常，否则放行
 */
@Component("operationAspect")
@Aspect
@Slf4j
public class GlobalOperationAspect {


    @Resource
    private RedisUtils redisUtils;

    //@Pointcut用于定义切点位置，不需要逻辑代码段，具体的逻辑在通知中
    @Pointcut("@annotation(com.wechat.annotation.GlobalInterceptor)")
    public void pointcut() {
    }

    /**
     * 前置通知
     * @param point--切入点对象
     */
    @Before("pointcut()")
    // JoinPoint point是切入点对象，包含了被拦截方法的所有信息
    public void interceptorDo(JoinPoint point) {
        try {
            // 1.获取被拦截的方法对象
            Method method = ((MethodSignature) point.getSignature()).getMethod();
            // 获取方法上GlobalInterceptor的注解实例，getAnnotation(GlobalInterceptor.class):用于获取指定类型的注解实例
            //这是 Method 类的方法,如果方法上没有该注解，返回 null
            // 2.获取注解实例，运用反射拦截方法
            GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class);
            if(null == interceptor){
                return;
            }

            // 3.执行校验登录（以及权限）
            if(interceptor.checkLogin() || interceptor.checkAdmin())
            checkLogin(interceptor.checkAdmin());

            //异常按顺序匹配
        } catch (BusinessException e) {//捕获业务异常：如未登录、权限不足
            log.error("全局拦截器异常", e);
            throw e;
        } catch (Exception e) {//捕获一般运行时的异常：如空指针、类型转换、SQL异常等等，统一处理成500
            log.error("全局拦截器异常", e);
            throw new BusinessException(ResponseCodeEnum.CODE_500);
        } catch (Throwable e) {//处理底层错误：OutOfMemoryError、StackOverflowError等
            log.error("全局拦截器异常", e);
            throw new BusinessException(ResponseCodeEnum.CODE_500);
        }

    }

    //具体登录校验逻辑
    private void checkLogin(Boolean checkAdmin) {
        //使用全局RequestContextHolder拿到request对象---运用到threadlocal，
        // 当一个HTTP请求到达时，DispatcherServlet会创建一个工作线程来处理这个请求，并将请求的HttpServletRequest对象即request对象保存在RequestContextHolder中
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String httpToken = request.getHeader("token");//前端每次请求都会从本地存储中获取token，然后携带token发送给请求

        TokenUserInfoDto validToken = (TokenUserInfoDto) redisUtils.get(Constants.REDIS_KEY_WS_TOKEN + httpToken);
        if (validToken == null) {//未登录异常
            throw new BusinessException(ResponseCodeEnum.CODE_901);
        }
        if(checkAdmin && !validToken.getAdmin()){//权限异常
            //这里的逻辑是：检查逻辑需要是管理员，但当前用户不是管理员，则抛出异常
            throw new BusinessException(ResponseCodeEnum.CODE_404);
        }
        //放行
    }

}